package br.usp.pcs.basys2;

import java.util.SortedSet;

import br.usp.pcs.basys2.engine.Engine;
import br.usp.pcs.basys2.event.EndOfProcessException;
import br.usp.pcs.basys2.event.InterruptionForFileActivityException;
import br.usp.pcs.basys2.event.InterruptionForNewProcessException;
import br.usp.pcs.basys2.event.InterruptionForPeripheralException;
import br.usp.pcs.basys2.info.FileActivityInfo;
import br.usp.pcs.basys2.info.PeripheralActivityInfo;
import br.usp.pcs.basys2.info.ProcessInfo;

public class BasysProcess {

	private ProcessInfo info;
	private Integer consumedTime = 0;
	private SortedSet<ActivityDuty> duties;
	private final DutyGenerator generator = Engine.generator;

	public Long getId() {
		return info.getId();
	}

	public Integer getSize() {
		return info.getSize();
	}

	public void runFor(Integer timeSlice)
			throws InterruptionForNewProcessException, EndOfProcessException,
			InterruptionForPeripheralException,
			InterruptionForFileActivityException {
		if (timeSlice == null) {
			throw new IllegalArgumentException();
		}
		if (consumedTime == 0L) {
			duties = generator.produceDuties(info);
			for (ActivityDuty duty : duties) {
				if (duty.getStartTime() > info.getCpuTotalTime()) {
					throw new IllegalStateException();
				}
			}

		}
		if (!duties.isEmpty()) {
			ActivityDuty next = duties.first();

			if (next.getStartTime() <= (consumedTime + timeSlice)) {
				duties.remove(next);

				Activity activity = next.getActivity();
				Integer time = next.getStartTime();
				int timeToWait = time-consumedTime;
				consumedTime = time;
				if (activity instanceof ProcessInfo) {
					ProcessInfo processInfo = (ProcessInfo) activity;
					BasysProcess basysProcess = new BasysProcess(processInfo);
					throw new InterruptionForNewProcessException(basysProcess,
							timeToWait);
				}

				if (activity instanceof PeripheralActivityInfo) {
					PeripheralActivityInfo info = (PeripheralActivityInfo) activity;

					throw new InterruptionForPeripheralException(this, timeToWait,
							info.getPeripheralName());
				}

				if (activity instanceof FileActivityInfo) {
					FileActivityInfo info = (FileActivityInfo) activity;

					throw new InterruptionForFileActivityException(this, timeToWait,
							info);
				}

				//
				// TODO IMPORTANTE: Registar os possiveis eventos!!! lancando
				// exception!
				//
			}
		}
		if (consumedTime + timeSlice >= info.getCpuTotalTime()) {
			int timeToWait = info.getCpuTotalTime() - consumedTime;
			if (timeToWait < 0)
				throw new IllegalStateException();
			throw new EndOfProcessException(this, timeToWait);
		} else {
			consumedTime += timeSlice;
		}
	}

	public BasysProcess(ProcessInfo processInfo) {
		this.info = processInfo;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((info == null) ? 0 : info.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final BasysProcess other = (BasysProcess) obj;
		if (info == null) {
			if (other.info != null)
				return false;
		} else if (!info.equals(other.info))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return info.getId().toString();
	}

	public ProcessInfo getInfo() {
		return info;
	}

}
